package fatboy.tools;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Random;

import fatboy.addons.Broadcast;
import fatboy.addons.Broadcast2;
import fatboy.addons.FinalListener;
import fatboy.addons.Listener;
import fatboy.hypeerweb.Node;

public class ProxyMaker implements Serializable {
	private static final long serialVersionUID = -8797538279872858768L;
	/**
	 * Running this causes all the proxies we use to be updated
	 * @param args unused command line arguments.
	 */
	public static void main(String[] args){
		createProxy(Broadcast.class);
		createProxy(Node.class);
		createProxy(Broadcast2.class);
		createProxy(Listener.class);
		createProxy(FinalListener.class);
	}
	/**
	 * makes a proxy for the input class
	 * @pre the input class has proper declaration for all methods
	 * and it has a writeReplace method which replaces it with a proxy
	 * generated from this method (Proxy(class name without path)) that accepts it's
	 * Object ID.  It also needs a no argument constructor that won't hurt things if
	 * it is called alot(essentially a do nothing one).
	 * (the methods don't need to be in when this method is ran but they do before the
	 * proxy will be used)
	 * @post the current class loader gets the a Proxy class loaded in, and a file in generated
	 * in the path src/packagePath. The class will begin to be overwritten
	 */
	public static void makeProxy(Class orignal){
		createProxy(orignal);
	}
	@SuppressWarnings("unchecked")
	private static void createProxy(Class orignal){
		String file = "";
		String temp = orignal.getName().substring(0,orignal.getName().length()-
				orignal.getSimpleName().length());
		temp = temp.replaceAll("[.]","/");
		String fileName = "src/"+temp+"Proxy"+orignal.getSimpleName();
		System.out.println(fileName);
		file +=  orignal.getPackage()+";\n";
		if(!orignal.getPackage().equals("package fatboy.communication;")){
			file += addImports();
		}
		file += createClassDeclartion(orignal)+"{\n";
		file += createFields();
		file += createConstructor(orignal)+"\n";
		file += makeCreateMethod(orignal)+"\n";
		file += ProxyMaker.serializationMethods()+"\n";
		
		Method[] methods = orignal.getDeclaredMethods(); 
		for(int i = 0; i < methods.length; i++){
			if(methods[i].getName().equals("writeReplace") || 
					Modifier.isStatic(methods[i].getModifiers())){
				continue;
			}
			file += createMethod(methods[i])+"\n";
		}
		file += "}\n";
		//System.out.println(file);
		
		PrintWriter pw = null;
		try {
			System.out.println("pwd: " + new File (".").getCanonicalPath()); //debug
			File file1 = new File(fileName+".java");
			file1.createNewFile();
			pw = new PrintWriter(file1);
			pw.println(file);
			pw.close();
			Runtime frame = java.lang.Runtime.getRuntime();
			frame.exec("javac -cp ../.. *.java");
			System.out.println("here");
		//	ClassLoader.getSystemClassLoader().loadClass(fileName+".class");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} /*catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} */catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	/**
	 * creates a method used by all other methods, it makes and sends a command across the network 
	 * @param orignal 
	 * @return string that is the method
	 */
	private static String makeCreateMethod(Class orignal){
		String method = "\tprivate static Object createCommand(ObjectID idIn, String meth," +
				" Class<Object>[] types,\n";
		method += "\t\tObject[] params, boolean returns, Location location){\n";
		method += "\t\tObject result = null;\n"+
			"\t\ttry {\n"+
			"\t\t\tCommand cmd = new Command(idIn,meth,types,params,\n\t\t\tProxy" +
			 orignal.getSimpleName()+".class.getSuperclass());\n";
		method += "\t\t\tlocation.sendCommand(cmd);\n"+
			"\t\t\tresult = location.recieveReturn();\n"+
			"\t\t\tlocation.close();\n"+
	"\t\t} catch (IOException e) {\n"+
		"\t\t\te.printStackTrace();\n"+
		"\t\t\t} catch (SecurityException e) {\n"+
		"\t\t\te.printStackTrace();\n"+
		"\t\t\t}catch (ClassNotFoundException e) {\n"+
		"\t\t\te.printStackTrace();\n"+
		"\t\t\t}\n"+
		"\t\t\treturn result;\n\t}";
		return method;
	}
	private static String addImports() {
		String imports = "import fatboy.communication.ObjectID;\n";
		imports += "import fatboy.communication.Location;\n";
		imports += "import fatboy.communication.RemoteLocation;\n";
		imports += "import fatboy.communication.Command;\n";
		imports += "import fatboy.communication.ResidentDatabase;\n";
		imports += "import java.io.IOException;\n";
		imports += "import java.io.Serializable;\n";
		return imports;
	}
	private static String createFields() {
		Random randy = new Random();
		return "\tprivate ObjectID id;\n\tprivate static final long serialVersionUID = 2159800065695342256L;\n";
	}
	private static String createClassDeclartion(Class orignal) {
		String declare = "public class Proxy" +orignal.getSimpleName()+
			" extends " + orignal.getSimpleName() + " implements Serializable";
		return declare;
	}
	private static String createConstructor(Class orignal) {
		String constructor = "\tpublic Proxy"+ orignal.getSimpleName() + "(ObjectID other){\n";
		constructor += "\t\tsuper(" ;
		constructor += ");\n";
		constructor += "\t\tid = new ObjectID(other);\n\t}";
		return constructor;
	}
	private static String createMethod(Method method) {
		String Method = "\t" +Modifier.toString(method.getModifiers());
		Method += " " + method.getReturnType().getName()+" "+method.getName()+"(";
		Class[] params = method.getParameterTypes();
		int i = 1;
		for(Class param : params){
			Method += param.getName() + " param" + i++ + ", "; 
		}
		if(params.length > 0){
			Method = Method.substring(0, Method.length()-2);
		}
		Class ret = method.getReturnType();
		Method += "){\n";
		if(ret != Void.TYPE){
			Method += "\t\tObject ret = null;\n";
		}
		Method += "\t\tString meth = \"" + method.getName()+"\";\n";
		Method += "\t\tClass[] types ={\n";
		for(Class param : params){
			Method += "\t\t\t"+param.getName() + ".class,\n";
		}
		if(params.length > 0){
			Method = Method.substring(0, Method.length()-2);
		}
		Method += "\n\t\t\t};\n";
		Method += "\t\tObject[] params = {\n";
		for(int j = 1; j <= params.length; j++){
			Method += "\t\t\tparam"+j+ ",\n";
		}
		if(params.length > 0){
			Method = Method.substring(0, Method.length()-2);
		}
		Method += "\n\t\t\t};\n";
		if(Modifier.isStatic(method.getModifiers())){
			Method += "\t\tObjectID id = new ObjectID(true);\n";
		}
		if(ret != Void.TYPE){
			Method += "\t\tret = ";
		}
		else{
			Method += "\t\t";
		}
		Method += "createCommand(id,meth,types,params,"+
			(method.getReturnType() != Void.TYPE) + ", getLocation(id));\n";
		
		if(method.getReturnType() != Void.TYPE){
			String retType = ret.getName();
			if(method.getReturnType().isPrimitive()){
				retType = replacePrimitiveWithWrapper(retType);
			}
			Method += "\t\treturn ("+retType+")ret;\n";
		}
		Method += "\t}";
		return Method;
	}
	private static String serializationMethods(){
		String methods = "\tpublic Object writeReplace(){\n\t\treturn this;\n\t}\n\tpublic Object readResolve(){"+
			"\n\t\tif(getLocation(this.id).isHere()){\n\t\t\treturn ResidentDatabase.getObject(id);\n\t\t}"+
			"\n\t\treturn this;\n\t}";
		methods += "\npublic Location getLocation(ObjectID id) {";
		methods += "\n\treturn new RemoteLocation((ObjectID)id);";
		methods += "\n\t}\n";
		return methods;
	}
	private static String replacePrimitiveWithWrapper(String primitive){
		String ret = primitive.substring(0,1).toUpperCase() + primitive.substring(1);
		if(ret.equals("Int")){
			ret = "Integer";
		}
		return ret;
	}
	
}
